use super::*;

/// A [Color Bitmap Data Table](
/// https://docs.microsoft.com/en-us/typography/opentype/spec/cbdt).
///
/// EBDT and bdat also share the same structure, so this is re-used for them.
#[derive(Clone, Copy)]
pub struct Table<'a> {
	cblc: cblc::Table<'a>,
	data: &'a [u8],
}

impl<'a> Table<'a> {
	/// Parses a table from raw data.
	pub fn parse(locations: cblc::Table<'a>, data: &'a [u8]) -> Option<Self> {
		Some(Self { cblc: locations, data })
	}

	/// Returns a raster image for the glyph.
	pub fn get(&self, glyph_id: GlyphId, pixels_per_em: u16) -> Option<Raster<'a>> {
		let location = self.cblc.get(glyph_id, pixels_per_em)?;
		let mut s = Stream::new_at(self.data, location.offset)?;
		let metrics = match location.format.metrics {
			cblc::MetricsFormat::Small => {
				let height = s.read::<u8>()?;
				let width = s.read::<u8>()?;
				let bearing_x = s.read::<i8>()?;
				let bearing_y = s.read::<i8>()?;
				s.skip::<u8>(); // advance
				cblc::Metrics {
					x: bearing_x,
					y: bearing_y,
					width,
					height,
				}
			}
			cblc::MetricsFormat::Big => {
				let height = s.read::<u8>()?;
				let width = s.read::<u8>()?;
				let hor_bearing_x = s.read::<i8>()?;
				let hor_bearing_y = s.read::<i8>()?;
				s.skip::<u8>(); // hor_advance
				s.skip::<i8>(); // ver_bearing_x
				s.skip::<i8>(); // ver_bearing_y
				s.skip::<u8>(); // ver_advance
				cblc::Metrics {
					x: hor_bearing_x,
					y: hor_bearing_y,
					width,
					height,
				}
			}
			cblc::MetricsFormat::Shared => location.metrics,
		};
		match location.format.data {
			cblc::BitmapDataFormat::ByteAligned { bit_depth } => {
				let row_len = (u32::from(metrics.width) * u32::from(bit_depth) + 7) / 8;
				let data_len = row_len * u32::from(metrics.height);
				let data = s.read_bytes(usize::num_from(data_len))?;
				Some(Raster {
					x: i16::from(metrics.x),
					// `y` in CBDT is a bottom bound, not top one.
					y: i16::from(metrics.y) - i16::from(metrics.height),
					width: u16::from(metrics.width),
					height: u16::from(metrics.height),
					pixels_per_em: location.ppem,
					format: match bit_depth {
						1 => RasterFormat::BitmapMono,
						2 => RasterFormat::BitmapGray2,
						4 => RasterFormat::BitmapGray4,
						8 => RasterFormat::BitmapGray8,
						32 => RasterFormat::BitmapPremulBgra32,
						_ => return None,
					},
					data,
				})
			}
			cblc::BitmapDataFormat::BitAligned { bit_depth } => {
				let data_len = {
					let w = u32::from(metrics.width);
					let h = u32::from(metrics.height);
					let d = u32::from(bit_depth);
					(w * h * d + 7) / 8
				};

				let data = s.read_bytes(usize::num_from(data_len))?;
				Some(Raster {
					x: i16::from(metrics.x),
					// `y` in CBDT is a bottom bound, not top one.
					y: i16::from(metrics.y) - i16::from(metrics.height),
					width: u16::from(metrics.width),
					height: u16::from(metrics.height),
					pixels_per_em: location.ppem,
					format: match bit_depth {
						1 => RasterFormat::BitmapMonoPacked,
						2 => RasterFormat::BitmapGray2Packed,
						4 => RasterFormat::BitmapGray4Packed,
						8 => RasterFormat::BitmapGray8,
						32 => RasterFormat::BitmapPremulBgra32,
						_ => return None,
					},
					data,
				})
			}
			cblc::BitmapDataFormat::PNG => {
				let data_len = s.read::<u32>()?;
				let data = s.read_bytes(usize::num_from(data_len))?;
				Some(Raster {
					x: i16::from(metrics.x),
					// `y` in CBDT is a bottom bound, not top one.
					y: i16::from(metrics.y) - i16::from(metrics.height),
					width: u16::from(metrics.width),
					height: u16::from(metrics.height),
					pixels_per_em: location.ppem,
					format: RasterFormat::PNG,
					data,
				})
			}
		}
	}
}

impl core::fmt::Debug for Table<'_> {
	fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
		write!(f, "Table {{ ... }}")
	}
}
